package proxy;


import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.URL;


public class ProxyThread extends Thread {
	private static final int BUFFER_SIZE = 32*1024;
    private FiltersManager filetersManager = FiltersManager.getFilterManager();
    private CacheManager cacheManager = CacheManager.getCacheManager();
    private byte[] responseByte;
	
	private Socket clientSocket = null;
    private Socket serverSocket;
    private InputStream serverInputStream;
	private OutputStream clientOutputStream;
    private InputStream responseStream;
	
    private static final boolean D = false;
    
    public ProxyThread(Socket socket) {
        super("ProxyThread");
        this.clientSocket = socket;
        
        //Hiding the system.err for clearer output
//        System.setErr(new PrintStream(new OutputStream() {
//            public void write(int b) {
//            }
//        }));
    }
    
    public void run(){   	
    	try{
    		String requestString = StringUtils.readRequestString(clientSocket.getInputStream());
    		String requestPath = StringUtils.getPath(requestString.split("\r\n")[0]);
    		
    		URL url = new URL(requestPath);
    		String hostname = url.getHost();
    		String path = "http://" + hostname; 
    		
    		path = requestPath.replaceAll(path,"");
    		
    		requestString = requestString.replaceFirst("keep-alive", "close");
    		requestString = requestString.replaceFirst("\\b(http://)[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]", path);
    		
    		serverSocket = new Socket(hostname, 80);
    		
    		serverInputStream = serverSocket.getInputStream();
			clientOutputStream = clientSocket.getOutputStream();
    		
    		//The host is blocked
    		if(filetersManager.isHostBlocked(hostname)){
    			responseStream = new FileInputStream(new File("Denial.html"));
    		} else {
    			if(D) System.out.println(hostname + path);
    			
    			String key = hostname + path;
    			if(cacheManager.exists(key)){
    				serverInputStream.close();
    				responseByte = cacheManager.getEntry(key).getData();
    				if(D) System.out.println("Retrieved from cache: " + key);
    			} else {
    				HTTPResponseWrapper responseWrapped = sendRequestToServer(requestString, serverSocket);
        			System.out.println(responseWrapped);
    				responseByte = responseWrapped.getResponseBytes();
    				if(isContentCachable(responseWrapped.getHeader("Cache-Control")))
        				storeContentIntoChache(key, responseWrapped);
    			}
    			
    			responseStream = new ByteArrayInputStream(cloneArray(responseByte));
    		}
    		
    		copyStream(responseStream, clientOutputStream);
    	} catch (Exception e){
    		//e.printStackTrace();
    	}
    	
    	//Free the allocated resources
    	try{
    		if(responseStream != null) responseStream.close();
    		if(clientOutputStream!= null) clientOutputStream.close();
		    if(serverInputStream != null) serverInputStream.close();
		    if(serverSocket != null) serverSocket.close();
	    	if(clientSocket != null) clientSocket.close();
    	} catch (Exception e){
    		//Do nothing
    	}
    }

    /**
     * Send the request string to the server using the given socket.
     */
    private HTTPResponseWrapper sendRequestToServer(String requestString, Socket serverSocket) throws Exception{
    	PrintWriter writer = new PrintWriter(new OutputStreamWriter(serverSocket.getOutputStream()));
		writer.write(requestString);
		writer.println("");
		writer.flush();
		
		byte[] response = readFully(serverSocket.getInputStream());
		HTTPResponseWrapper result = new HTTPResponseWrapper(cloneArray(response));
		return result;
    }
    
    /**
     * Store the given response into the cache manager with the given key.
     */
    private void storeContentIntoChache(String key, HTTPResponseWrapper responseWrapped) throws Exception{
		String expireString = responseWrapped.getHeader("Expires");
		if(!expireString.equals("")){
			CacheEntry entry = new CacheEntry(expireString, cloneArray(responseByte));
			cacheManager.storeEntry(key, entry);
			if(D)System.out.println("Saved in cache: " + key);
		}
    }
    /**
     * Copy an InputStream into an OutputStream. 
     */
    public static void copyStream(InputStream input, OutputStream output) throws IOException{
	    byte[] buffer = new byte[BUFFER_SIZE];
	    int bytesRead;
	    while ((bytesRead = input.read(buffer)) != -1){
	        output.write(buffer, 0, bytesRead);
	    }
	    
	    output.flush();
	}
    
    /**
     * Return true if the content can be stored in a public cache.
     */
    public boolean isContentCachable(String cachable){
    	cachable = cachable.trim();
    	if(cachable.startsWith("public")) return true;
    	return false;
    }
    
    /**
     * Create a copy of the input array of bytes.
     */
    public static byte[] cloneArray(byte[] array){
    	byte[] result = new byte[array.length];
    	for (int i = 0; i < result.length; i++) {
			result[i] = array[i];
		}
    	return result;
    }
    
    /**
     * Fully read an input stream into a byte array.
     */
    public static byte[] readFully(InputStream input) throws IOException{
	    byte[] buffer = new byte[BUFFER_SIZE];
	    int bytesRead;
	    ByteArrayOutputStream output = new ByteArrayOutputStream();
	    while ((bytesRead = input.read(buffer)) != -1){
	        output.write(buffer, 0, bytesRead);
	    }
	    
	    output.flush();
	    return output.toByteArray();
	}    
}